Skip to content

Enable Union notation at Python-scope for warp arrays#1549

Open
FabienPean-Virtonomy wants to merge 1 commit into
NVIDIA:mainfrom
Virtonomy:FabienPean/union-type-hint
Open

Enable Union notation at Python-scope for warp arrays#1549
FabienPean-Virtonomy wants to merge 1 commit into
NVIDIA:mainfrom
Virtonomy:FabienPean/union-type-hint

Conversation

@FabienPean-Virtonomy

@FabienPean-Virtonomy FabienPean-Virtonomy commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

Array subscript annotations such as wp.array[...] still return lightweight annotation instances but they now support PEP 604 union expressions at Python scope through instance operator support (for example, wp.array[float] | float and float | wp.array[float]).

Closes #1548

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.
  • CHANGELOG.md is updated for any user-facing changes under the Unreleased section.

Validation Summary

  • Updated and added tests in warp/tests/test_subscript_types.py.
  • Ran:
    uv run test_subscript_types.py

Bug Fix Repro

import warp as wp
wp.init()
# PEP 604 union support at Python scope
T = wp.array[float] | float
U = float | wp.array[float]
# Union annotations remain invalid in Warp codegen signatures

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed runtime support for PEP 604 union expressions (|) with array type annotations, enabling syntax like wp.array[float] | float in Python method contexts.
  • Tests

    • Added tests validating union operator support with array annotations and confirming unions are supported only at Python scope.

@copy-pr-bot

copy-pr-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 4835d5d0-13e1-44bf-9295-a368ae9a6b97

📥 Commits

Reviewing files that changed from the base of the PR and between 379fb1e and 565f925.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • warp/_src/types.py
  • warp/tests/test_subscript_types.py
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • warp/tests/test_subscript_types.py

📝 Walkthrough

Walkthrough

Adds __or__ and __ror__ operator overloads to _ArrayAnnotationBase so wp.array[...] annotation instances can participate in PEP 604 union expressions (e.g., wp.array[float] | float), producing typing.Union[...] types. Guards get_type_code() against Union annotations by raising TypeError early. Adds two unit tests and a changelog entry.

Changes

PEP 604 Union Support for Array Annotations

Layer / File(s) Summary
__or__/__ror__ on _ArrayAnnotationBase and codegen guard
warp/_src/types.py
_ArrayAnnotationBase gets __or__ and __ror__ returning typing.Union[self, other], and get_type_code() adds an early branch that raises TypeError when the annotation's origin is Union.
Tests and changelog
warp/tests/test_subscript_types.py, CHANGELOG.md
test_annotation_union_operator validates PEP 604 union expressions for wp.array and wp.indexedarray (both operand orders, None unions, and chaining); test_annotation_union_invalid_for_codegen confirms codegen raises RuntimeError. Union is added to the typing import. Changelog records the fix under Unreleased.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling PEP 604 union notation for warp array annotations at Python scope, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fully addresses issue #1548 by implementing __or__ and __ror__ methods on _ArrayAnnotationBase to enable PEP 604 union expressions (e.g., wp.array[float] | float) at Python scope.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objective of enabling union notation for warp arrays. The CHANGELOG, types.py modifications, and test additions are all within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
warp/_src/types.py (1)

5132-5153: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Returning classes here breaks the existing None-argument inference path.

After this change, wp.array[...] is a class object, but infer_argument_types() still reconstructs array templates with type(t)(dtype=t.dtype, ndim=t.ndim) on Line 7163. For these annotations, type(t) is now _ArrayAnnotationTypeMeta, so passing None for an array-typed parameter will raise instead of preserving the template type.

At minimum, the downstream reconstruction needs to special-case is_array_annotation(t) and reuse t (or rebuild it via _make_array_annotation_type(...)) instead of calling type(t)(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@warp/_src/types.py` around lines 5132 - 5153, The change made in
_parse_array_subscript returns class-like objects which breaks
infer_argument_types' reconstruction that calls type(t)(dtype=..., ndim=...)
(type(t) is now _ArrayAnnotationTypeMeta); update infer_argument_types to
special-case array annotations by using is_array_annotation(t) and either reuse
the existing annotation object t directly when filling in None arguments or
rebuild the annotation via
_make_array_annotation_type(_ARRAY_ANNOTATION_MAP.get(base), dtype=t.dtype,
ndim=t.ndim) instead of calling type(t)(...), ensuring array templates preserve
their original semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@warp/_src/types.py`:
- Around line 5112-5129: _make_array_annotation_type currently constructs a new
_ArrayAnnotationTypeMeta on every call causing identity instability; add a cache
(e.g., a module-level dict or an attribute on ann_base) keyed by the normalized
dtype and ndim (use the same dtype = dtype if dtype is Any else
type_to_warp(dtype) logic and the resolved ndim) and return the cached class
when present instead of allocating a new one; update the function to compute the
cache key (ann_base, dtype, ndim), check and return from cache, and only create
and store a new _ArrayAnnotationTypeMeta if the key is missing so repeated
subscripts (e.g. wp.array[float]) yield identical types.
- Around line 5064-5070: The __ctype__ implementation for array-like classes
currently returns a bare array_t() which discards annotated rank/shape/strides;
change __ctype__ (using cls._concrete_cls, array_t(), indexedarray_t,
ARRAY_MAX_DIMS) to preserve the annotated ndim/shape/strides the same way the
old annotation path did—read cls.ndim (falling back to Any), cls.shape and
cls.strides if present (or default placeholders) and construct array_t(...) with
those values instead of returning array_t() so codegen/native consumers retain
the array rank information.

---

Outside diff comments:
In `@warp/_src/types.py`:
- Around line 5132-5153: The change made in _parse_array_subscript returns
class-like objects which breaks infer_argument_types' reconstruction that calls
type(t)(dtype=..., ndim=...) (type(t) is now _ArrayAnnotationTypeMeta); update
infer_argument_types to special-case array annotations by using
is_array_annotation(t) and either reuse the existing annotation object t
directly when filling in None arguments or rebuild the annotation via
_make_array_annotation_type(_ARRAY_ANNOTATION_MAP.get(base), dtype=t.dtype,
ndim=t.ndim) instead of calling type(t)(...), ensuring array templates preserve
their original semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 25e54390-be3e-49bd-94f0-acb5c563f40b

📥 Commits

Reviewing files that changed from the base of the PR and between be5e930 and 2b76761.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • warp/_src/types.py
  • warp/tests/test_subscript_types.py

Comment thread warp/_src/types.py Outdated
Comment thread warp/_src/types.py Outdated
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

Adds PEP 604 union support at Python scope for Warp array annotation instances by implementing __or__ and __ror__ on _ArrayAnnotationBase, both returning typing.Union. A corresponding guard in get_type_code raises a clear TypeError when a union type accidentally reaches Warp codegen, which get_signature then wraps into a RuntimeError with the argument name and function context.

  • _ArrayAnnotationBase.__or__ / __ror__: delegate to typing.Union, enabling wp.array[float] | float and float | wp.array[float] at Python annotation scope.
  • get_type_code: new typing.Union guard provides a descriptive error if a union annotation is mistakenly used in a kernel/function signature; does not yet cover types.UnionType (Python 3.10+ native pipe unions on plain built-in types).

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped, adds no new codegen paths, and the new Union guard is purely defensive.

The two new dunder methods delegate entirely to typing.Union, the guard in get_type_code only adds a clearer error path that was previously a generic 'Unrecognized type' fall-through, and all new behaviour is covered by tests. There are no regressions on existing code paths.

No files require special attention.

Important Files Changed

Filename Overview
warp/_src/types.py Adds or/ror to _ArrayAnnotationBase (returning typing.Union) and a new Union guard in get_type_code; changes are minimal and correct for the stated use case.
warp/tests/test_subscript_types.py Adds two new test methods covering union operator in both operand orders, None variants, chaining, and the expected RuntimeError on codegen; test expectations are consistent with the implementation.
CHANGELOG.md Adds a changelog entry under Unreleased with an accurate description and correct issue link.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as Python scope
    participant Ann as _ArrayAnnotationBase
    participant TU as typing.Union
    participant GTC as get_type_code
    participant GS as get_signature

    User->>Ann: "wp.array[float] | float"
    Ann->>TU: Union[self, float]
    TU-->>User: typing.Union annotation (valid at Python scope)

    User->>GS: "@wp.func with Union annotation"
    GS->>GTC: get_type_code(Union[...])
    GTC-->>GS: TypeError(Union type annotations are only supported at Python scope...)
    GS-->>User: RuntimeError(Failed to determine type code for argument 'a'...)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User as Python scope
    participant Ann as _ArrayAnnotationBase
    participant TU as typing.Union
    participant GTC as get_type_code
    participant GS as get_signature

    User->>Ann: "wp.array[float] | float"
    Ann->>TU: Union[self, float]
    TU-->>User: typing.Union annotation (valid at Python scope)

    User->>GS: "@wp.func with Union annotation"
    GS->>GTC: get_type_code(Union[...])
    GTC-->>GS: TypeError(Union type annotations are only supported at Python scope...)
    GS-->>User: RuntimeError(Failed to determine type code for argument 'a'...)
Loading

Reviews (4): Last reviewed commit: "Implement union operator support for arr..." | Re-trigger Greptile

Comment thread warp/_src/types.py Outdated
Comment thread warp/_src/types.py Outdated
Comment on lines +5028 to +5032
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The __eq__ implementation returns False (not NotImplemented) when other is a type but is not an _ArrayAnnotationBase subclass. Python convention is to return NotImplemented when a comparison is not meaningful for the operand types, so the other side can still be tried. Returning False suppresses that fallback. In practice this rarely matters here (built-in type.__eq__ also falls back correctly), but it diverges from the standard contract and could silently short-circuit user-defined __eq__ on custom type objects compared against annotation types.

Suggested change
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return False
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return NotImplemented

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@shi-eric

Copy link
Copy Markdown
Contributor

/ok to test 2b76761

@shi-eric

Copy link
Copy Markdown
Contributor

Thanks for the fix. It works and the tests are thorough. The root cause is just that wp.array[float] is an instance, so | has nothing to dispatch to. Why not give the annotation base __or__/__ror__ instead of promoting the annotations to types? Since these unions are only valid as annotations on plain Python functions (codegen doesn't support union types anyway), that would keep the annotation a lightweight instance and avoid the metaclass, identity cache, and call-site churn here. Was there something that needs the annotation to actually be a type?

@FabienPean-Virtonomy FabienPean-Virtonomy force-pushed the FabienPean/union-type-hint branch from 3d8266f to 379fb1e Compare June 15, 2026 12:31
@FabienPean-Virtonomy

FabienPean-Virtonomy commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Two factors that led to this version:

  • The pedantic reading of the Python documentation

    Union Type
    A union object holds the value of the | (bitwise or) operation on multiple type objects. These types are intended primarily for type annotations. When I read the PEP requirement, type annotation

  • My stronger C++ background compared to Python

Your option leads indeed to a much smaller fix. The differences are that wp.array[float] is wp.array[float] == False and maybe related to type checkers. Using type hint without type arguments still weirds me out though :) The branch has now your simpler version

@FabienPean-Virtonomy FabienPean-Virtonomy changed the title Return types for array subscript annotations (GH-1548) Enable Union notation at Python-scope for warp arrays Jun 15, 2026
@shi-eric

Copy link
Copy Markdown
Contributor

Thanks for the fix! It looks good.

Could you please rebase your changes on main so we can get this merged? This is a bit odd because we merge changes through our internal repo first, so the branch should have a minimal number of commits (your branch already satisfies this) and has to be up-to-date with main (I see some merge conflicts flagged).

We can revisit whether these annotations should be real type objects in the future if the current approach runs into a concrete limitation, but I think the simpler approach addresses the immediate need.

…d tests (NVIDIAGH-1548)

Signed-off-by: Fabien Péan <pean@virtonomy.io>
@FabienPean-Virtonomy FabienPean-Virtonomy force-pushed the FabienPean/union-type-hint branch from 379fb1e to 565f925 Compare June 16, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Union syntax raises TypeError

2 participants